package crawlerv2;


import java.io.*;
import java.net.*;
import java.util.Calendar;
import java.util.GregorianCalendar;

/**
 *
 * @author Samer Al-Kiswany
 * modified by: Miguel Guanlao
 */
public class Crawler {
    private CrawlResult cResult;
    //hostname and ipaddr are added
    protected String HostName;
    protected String IPAddr;
    //Calendar to get time when node is crawled
	protected Calendar crawlCal;

    public Crawler() {
        cResult=new CrawlResult();
        HostName=new String();
        IPAddr=new String();
        crawlCal = new GregorianCalendar();
    }
    
    public CrawlResult crawl(String ipAddress,int port){
        
        System.out.println("Crawling "+ipAddress+":"+port);
        //print out when node is being crawled
		String crawlDate = crawlCal.get(Calendar.HOUR_OF_DAY)+
        	":"+crawlCal.get(Calendar.MINUTE);
        System.out.println("Crawling peer at time "+crawlDate);
        String nodePeers=crawlPeers(ipAddress,port);
        if(nodePeers==null){
            System.out.println("Failed to crawl the peer");
            return null;
        }
        if (nodePeers.length()>0){
            parsePeers(nodePeers);            
        }
        else{
            System.out.println("Error : Recieved zero-length list of peers");
            return cResult;
        }        
        
        byte[] flist=listFiles(ipAddress,port);
        if (flist.length>0){
            parseFilesList(flist);            
        }

        return cResult;
    }
    

    //changed from private to protected
    protected String crawlPeers(String ipAddress,int port){
        InputStream in;
        OutputStream out;
        Socket socket;
        
        try {
            socket = new Socket(ipAddress, port);
//            System.out.println("Connected to node : "+ipAddress+" on port "+port);
            socket.setSoTimeout(5000); //set timeout to 5 seconds in case transaction takes too long
            
            in = socket.getInputStream();
            out = socket.getOutputStream();
            
        } catch (UnknownHostException ex) {
            System.out.println("Error: Failed to connect to node "+ipAddress+":"+port);
            return null;
//            ex.printStackTrace();
        } catch (IOException ex) {
            System.out.println("Error: Failed to connect to node "+ipAddress+":"+port);
            return null;
//            ex.printStackTrace();
        }
       
       //Get and display local IP address (where crawler is communicating from)
       String LocalIP= getLocalIP(socket);
       System.out.println("Crawler communicating from : "+ LocalIP);
       String GNodetName=socket.getInetAddress().getHostName();
       HostName = GNodetName;
       IPAddr = getIPAddress(socket);
       //System.out.println("Host name is : "+GNodetName);
        String response=new String();
        StringBuffer request = new StringBuffer();
        request.append("GNUTELLA CONNECT/0.6\r\n" +
                        "User-Agent: UBCECE (carwl)\r\n" +
                        "Query-Routing: 0.2\r\n"+
                        "X-Ultrapeer: False\r\n"+
                        "Crawler: 0.1\r\n" +
                        "\r\n");
   
        byte[] bytes = request.toString().getBytes();
        
        try {
            out.write(bytes);
            out.flush();
        } catch (IOException ex) {
            System.out.println("Error: Failed to send the crawl message to "+ipAddress+":"+port);
//            ex.printStackTrace();
            return null;
        }

        String responseLine=new String();
        while (true){
            try {
                responseLine = ByteOrder.readLine(in);
            } catch (IOException ex) {
                System.out.println("Error: Failed to recieve the responce from the node "+ipAddress+":"+port);
//                ex.printStackTrace();
                return null;
            }
            
            // reached the end of the responce
            if (responseLine.length()<2){
                break;
            }
            response+=responseLine;
            response+="\n";
        }

        try {
            socket.close();
            in.close();
            out.close();
        } catch (IOException ex) {
            ex.printStackTrace();
            return null;
        }
        
        return response;
    }
    
    //This method is to remove hostname next to IP address as well as to append
    //socket to end of IP address
    private String getIPAddress(Socket socket) {
        String IPAddress = new String();
    	IPAddress = socket.getInetAddress().toString() + ":" + socket.getPort() ;
    	if(IPAddress.indexOf("/")!=-1) IPAddress = 	IPAddress.substring(IPAddress.indexOf("/")+1); 
		return IPAddress;
	}
    
    //This is used to get the LOCAL IP address (from where crawler is communicating from)
    private String getLocalIP(Socket socket) {
        String IPAddress = new String();
    	IPAddress = socket.getLocalAddress().toString() + ":" + socket.getPort() ;
    	if(IPAddress.indexOf("/")!=-1) IPAddress = 	IPAddress.substring(IPAddress.indexOf("/")+1); 
		return IPAddress;
	}

	private void parsePeers(String topoCrawl){
        int beginIndex;
        int endIndex;
        String upeers=new String();
        String leaves=new String();
        
        beginIndex=topoCrawl.indexOf("Peers:");
        endIndex=topoCrawl.indexOf("\n",beginIndex);
        upeers=topoCrawl.substring(beginIndex+6,endIndex);
        upeers=upeers.trim();
        
        beginIndex=topoCrawl.indexOf("Leaves:");
        endIndex=topoCrawl.indexOf("\n",beginIndex);
        leaves=topoCrawl.substring(beginIndex+7,endIndex);
        leaves=leaves.trim();        
        
        cResult.setUltrapeers(upeers);
        cResult.setLeaves(leaves);
    }
   
    //changed from private to protected
    protected byte[] listFiles(String ipAddress,int port){
        InputStream in;
        OutputStream out;
        Socket socket;
        
        try {
            socket = new Socket(ipAddress, port);
//            System.out.println("Connected to node : "+ipAddress+" on port "+port);

            in = socket.getInputStream();
            out = socket.getOutputStream();
            
        } catch (UnknownHostException ex) {
            System.out.println("Error: Failed to connect to node "+ipAddress+":"+port);
            return null;
//            ex.printStackTrace();
        } catch (IOException ex) {
            System.out.println("Error: Failed to connect to node "+ipAddress+":"+port);
            return null;
//            ex.printStackTrace();
        }
        
        
        String response=new String();
        StringBuffer request = new StringBuffer();
        
        String localAddress=socket.getLocalAddress().getHostAddress();
        
        request.append("GET / HTTP/1.1\r\n" +
                        "Host: "+localAddress+"\r\n"+
                        "User-Agent: UBCECE/0.1\r\n" +
                        "Accept: text/html, application/x-gnutella-packets\r\n"+
                        "Connection: close\r\n"+
                        "\r\n");
        
        byte[] bytes = request.toString().getBytes();
        
        try {
            out.write(bytes);
            out.flush();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        
        // a better design is to parse the message as you read it, rather than allocating a huge array
        byte[] buffer=new byte[1000000];
        int numOfBytes=0;
        int tmpInt=0;
        while (true){
            try {
                numOfBytes = ByteOrder.readBuffer(in,buffer);
                tmpInt =in.available();
            } catch (IOException ex) {
                ex.printStackTrace();
                return null;
            }

            if (tmpInt<=0){
                break;                
            }
        }

        try {
            socket.close();
            in.close();
            out.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        
        byte[] result=new byte[numOfBytes];

        System.arraycopy(buffer,0,result,0,numOfBytes);
    
        return result;
    }
    
    private void parseFilesList(byte[] flist){

        int beginIndex=findEndOfHeader(flist);
        if (beginIndex<1){
            return;
        }
        String Header=new String(subBuffer(flist,0,beginIndex));
        int temp1=Header.indexOf("Server:");
        int temp2=Header.indexOf('\n',temp1);
        Header=Header.substring(temp1+8,temp2);
        Header.trim();
        temp1=Header.indexOf('/');
        if (temp1==-1) 
            temp1=Header.indexOf(' ');
        
        Header=Header.substring(0,temp1);
        cResult.setAgent(Header);
        flist=subBuffer(flist,beginIndex,flist.length);
        int length;
        String msg=new String();
        
        while (flist.length>0){
            flist=subBuffer(flist,19,flist.length);
            msg=new String(flist);
            length=ByteOrder.leb2int(subBuffer(flist,0,4),0);

            byte[] qhit=subBuffer(flist,4,length);
            processQueryHit(qhit);
            flist=subBuffer(flist,length+4,flist.length);
            if (flist==null)
                break;            
        }
    }
    
    private void processQueryHit(byte[] qhit){
        int beginIndex;
        int endIndex;
        int numOfFiles=qhit[0];
        String sentList=new String();
        String filesList=new String();
        String msg=new String();


        qhit=subBuffer(qhit,11,qhit.length);
        msg=new String(qhit);

        byte[] fname=new byte[512];
        int num=0;
        while (qhit.length>0 && num<numOfFiles){
            byte[] test=subBuffer(qhit,0,4);
            qhit=subBuffer(qhit,8,qhit.length);
            int x=ByteOrder.leb2int(test,0);
            
            endIndex=bufferIndexOf(qhit,'\0');
            fname=subBuffer(qhit,0,endIndex);
            qhit=subBuffer(qhit,endIndex+1,qhit.length);
            msg=new String(fname);
            filesList+=msg;
            filesList+='\0';
                        
            endIndex=bufferIndexOf(qhit,'\0');
            qhit=subBuffer(qhit,endIndex+1,qhit.length);
            num++;
            if (qhit==null)
                break;
        }
        
        cResult.addNumOfFiles(numOfFiles);
        cResult.addFilesList(filesList);
    
    }
    
    //changed from private to protected
    protected int findEndOfHeader(byte[] packet){

        for (int i=0;i<=packet.length-4;i++){
            if (packet[i]=='\r' && packet[i+1]=='\n' && packet[i+2]=='\r' && packet[i+3]=='\n'){
                return i+4;
            }
        }
        return -1;        
    }
    
    //changed from private to protected
    protected byte[] subBuffer(byte[] buffer,int beginIndex,int endIndex){
        
        if (beginIndex>buffer.length){
            return null;
        }
        if (endIndex>buffer.length){
            endIndex=buffer.length;
        }
        int bLen=endIndex-beginIndex;
        byte[] result=new byte[bLen];
     
        System.arraycopy(buffer,beginIndex,result,0,bLen);
        
        return result;
    }
    
    //changed from private to protected
    protected int bufferIndexOf(byte[] buffer,char c){
        
        for (int i=0;i<buffer.length;i++){
            if (buffer[i]==c){
                return i;
            }
        }
        return -1;
    }
    
}
